﻿#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//字符指针变量
//int main()
//{
//	char arr[10] = "abcdef";
//	char* p1 = arr; //不是两个例外，即arr就是字符数组首元素'a'的地址
//	*p1 = 'w'; //会将字符数组首元素'a'转变成'w'
//
//	const char* p2 = "abcdef"; //之前我们已经知道const放在左边是让*p禁止修改
//	//*p2 = 'w'; //err         //这里不加const也不能修改*p2，原因是p2所指向的内容是常量字符串，而常量字符串本身是不能修改的
//	printf("%s\n", p1);        //上面的p1指向的内容是字符数组，字符数组内的内容是可以修改的，这是两者的区别
//	printf("%s\n", p2);        //加上const只是让人知道 *p2 是不能修改的
//	//打印字符串的时候字符指针变量是不能解引用的，
//	//原因是不管是字符数组还是常量字符串，在存放到字符指针变量时都是等同于存放首字符的地址
//	//如果解引用，得到的也只是首字符，%s打印肯定就有问题
//	return 0;
//}

//《剑指offer》中收录了⼀道和字符串相关的笔试题
//int main()
//{
//	char str1[] = "hello bit.";
//	char str2[] = "hello bit.";
//	const char* str3 = "hello bit.";
//	const char* str4 = "hello bit.";
//	if (str1 == str2)
//		printf("str1 and str2 are same\n");
//	else
//		printf("str1 and str2 are not same\n");
//
//	if (str3 == str4)
//		printf("str3 and str4 are same\n");
//	else
//		printf("str3 and str4 are not same\n");
//	return 0;
//}
    //得到的结果为str1 and str2 are not same ， str3 and str4 are same
    //原因是str1、str2是字符数组，而str3、str4是字符指针变量，两者是完全不同的东西
    //而之前我们知道只要创造新数组就会开辟一个新空间，所以str1和str2本来就在两个不同的地址独立存放
    //又因为数组名就是数组首元素的地址，本质就是地址比较，故str1 == str2是不成立的
	//只是str1和str2两个数组内部存放的内容("hello bit.")是一样的
    //str3和str4是字符指针变量，两者表示的内容就是地址，而这个地址就是字符串首字符的地址，这是一样的，故str3 == str4成立

//数组指针变量
    //之前我们学习了指针数组，指针数组是⼀种数组，数组中存放的是地址（指针）。
    //数组指针变量是一种指针：存放的应该是数组的地址，能够指向数组的指针变量。
//int main()
//{
//    int arr[10] = { 0 };
//    int (*p)[10] = &arr; //&arr --> 取出的是数组的地址 
//              //arr  --> 取出的是数组首元素的地址 
//    return 0;
//}
    //int* p1[10]; //p1是指针数组 -- 存放指针的数组
    //可以这样类比：int p1[10]是整型数组，p1[10]里面存放的内容是 int 类型
    //             int* p1[10]是指针数组，p1[10]里面存放的内容是 int* 类型(即指针类型)
    
    //int (*p2)[10]; //由于有括号先考虑，p2是指针变量，指向的对象是数组
    //可以这样类比：int* p2，p2是指针变量，p2所指向的对象的类型是 int 类型
    //        int (*p2)[10]，p2是指针变量，p2所指向的对象的类型是 int[10] 类型(即数组类型，数组的内容是 int 类型)
    //        *p2就可以看作成是数组名
    
    //这里要注意：[]的优先级要高于*号的，所以必须加上（）来保证p先和*结合，使p2为数组指针

//数组指针变量怎么初始化
//int main()
//{
//    int arr[10] = { 0 };
//    int* p1 = arr; //为数组首元素的地址
//      //int*  int*
//    printf("%p\n", p1);
//    printf("%p\n", p1 + 1); //跳过4/8个字节
//
//    int (*p2)[10] = &arr;
//    //p2的类型：int (*)[10]
//    printf("%p\n", p2);
//    printf("%p\n", p2 + 1); //跳过40个字节
//    return 0;
//}

//⼆维数组传参的本质
    //过去我们有⼀个二维数组的需要传参给一个函数的时候，我们是这样写的：
//void Print(int arr[3][5], int r, int c)
//{
//    int i = 0;
//    int j = 0;
//        for (i = 0; i < r; i++)
//    {
//        for (j = 0; j < c; j++)
//        {
//            printf("%d ", arr[i][j]);
//        }
//        printf("\n");
//    }
//}
    //在这个函数中形参写成的是数组的形式
    //按照之前写一维数组的传参，是不是也有一种写成指针的形式呢？是有的，但有更多要注意的点：

    //之前我们就知道一维数组的数组名除开两个例外就是数组首元素的地址
    //二维数组和一维数组一样，除开两个例外就是数组首元素的地址
    //那首元素到底指的是什么呢？学习二维数组时我们知道：如果我们把一维数组做为数组的元素，这时候就是二维数组
    //所以二维数组的首元素其实是第一行一维数组，故数组首元素地址就是第一行一维数组的地址(类型为数组指针类型)
    //这样就和我们刚刚所学的数组指针变量串起来了：
//void Print(int (*arr)[5], int r, int c) //这就是二维数组在函数传参的指针形式(数组指针类型)
//{
//    for (int i = 0; i < r; i++)
//    {
//        for (int j = 0; j < c; j++)
//        {
//            printf("%d ", (*(arr + i) + j)); // == arr[i][j]
//            //arr + i就是指针加整数的形式，而arr的内容的类型是int[5]类型(数组类型)，则加i指的是跳过i*20个字节对应的地址
//            //再解引用*(arr + i),得到的结果是一维数组，而一维数组又还是该数组首元素地址，即指针
//            //所以*(arr + i)所得到的仍然是一个指针，*(arr + i) + j自然能想到还是指针加整数的形式
//            //但此时*(arr + i)是一维数组，故其内容的类型是int类型，则加j指的是跳过j*4个字节对应的地址
//            //再解引用最终得到的就是对应位置的整数了
//        }
//     }
//}
//int main()
//{
//    int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
//    Print(arr, 3, 5); //⼆维数组传参本质上也是传递了地址，传递的是第一行这个⼀维数组的地址
//    return 0;         //而第一行的地址的类型就是数组指针类型 int(*)[5]
//}

//函数指针变量
    //函数指针变量，指针指向函数，是用来存放函数地址的，未来通过地址能够调用函数的。
    //之前我们已经知道&数组名取出的是数组的地址
    //                 数组名取出的是数组首元素的地址，两者虽然输出的值是一样的，但表示的意义是不一样的
    //那么函数是否有地址呢？是否和数组名类似呢？我们做个测试：
//int Add(int x, int y)
//{
//    return x + y;
//}
//int main()
//{
//    printf("&Add = %p\n", &Add);
//    printf("Add  = %p\n", Add);
//    return 0;
//}
    //通过输出的结果我们发现 &函数名 和 函数名 两者的地址也是一样的
    //但其实不同于 数组名 和 &数组名 ，&函数名 和 函数名 两者表示的都是函数的地址，而没有函数首元素地址这种说法

//函数指针变量的创建
    //如果我们要将函数的地址存放起来，就得创建函数指针变量，函数指针变量的写法其实和数组指针变量非常类似。如下：
//int Add(int x, int y)
//{
//    return x + y;
//}
//int main()
//{
//    //int arr[10] = { 0 };
//    //int (*pa)[10] = &arr;
//    //&arr取出的是整个数组的地址，指针类型是数组指针类型，把pa去掉：int (*)[10]就是数组指针类型
//
//    int (*pf)(int, int) = &Add; //pf是专门用来存放函数的地址的，pf就是函数指针变量
//    //和数组类比：把pf去掉，int (*)(int, int)就是函数指针类型
//    //(*pf)后面括号的形式与函数形参内容一一对应，同理(*pf)前面的形式与函数返回值类型保持一致
//    //比如一个函数：char* test(char r, int c) --> 对应的函数指针变量就是：char* (*pf)(char, int)
//    return 0;
//}

//函数指针变量的使用
//int Add(int x, int y)
//{
//    return x + y;
//}
//int main()
//{
//    int (*pf)(int, int) = &Add;
//
//    int c = Add(3, 4); //函数名调用
//    printf("%d\n", c);
//
//    int d = (*pf)(3, 4); //函数指针调用
//    //pf是函数指针变量，存放的是函数的地址，对其解引用*(pf)得到的就是函数名
//    printf("%d\n", d);
//
//    int e = pf(3, 4);
//    printf("%d\n", e); //观察输出的结果我们会发现c，d，e三个变量的值竟然是一样的
//    //其实本质原因是前面我们知道 函数名 和 &函数名 两者的地址是完全一样的，说明函数名就是函数名的地址
//    //而 pf 是函数名的地址，*(pf)是函数名 --> 故 pf 和 *(pf) 是完全等价的
//    return 0;
//}

//关于函数指针两段有趣的代码
// 1.(* ( void (*)() ) 0)(); //解释一下这段代码
    //void (*)() --> 函数指针类型
    //( void (*)() ) -->  把一个类型括号括起来：强制类型转换
    //( void (*)() ) 0 --> 0原本是 int 类型，将0由 int 类型强制转换成 void (*)() 函数指针类型
    //这就意味着我们假设0这个元素的地址放着无参、返回值为void的函数
    //在上面我们已经知道函数名就是函数名的地址，即 pf 和*(pf) 是完全等价的
    //所以( void (*)() ) 0 和 (* ( void (*)() ) 0) 也是完全等价的
    //这段代码表示的意思就是调用0地址处放的这个函数

//2.void (* signal( int , void(*)(int) ) )(int);
    //signal( int , void(*)(int) ) -->  这是一个命名为signal的函数，
    //                                  两个形参一个类型是 int 类型，另一个是 void(*)(int) 类型(函数指针类型)
    //将signal( int , void(*)(int) )去掉得到的void (*)(int)其实是这个函数的返回值的类型
    //写成这个样子其实很难理解，写成void(*)(int) signal(int , void(*)(int))可能就清晰了，但这种写法是错误的，只是方便理解
    //所以void (* signal( int , void(*)(int) ) )(int);这段代码表示的就是一个“函数声明”

//typedef 关键字
    //typedef 是用来类型重命名的，可以将复杂的类型，简单化。
    //比如，unsigned int 写起来不方便，如果能写成 uint 就方便多了，那么可以使用：
    typedef unsigned int u_int; ////将unsigned int 重命名为uint 
    typedef int* pint_t;

    //但是对于数组指针和函数指针稍微有点区别：
    //比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ，那需要这样写：
    typedef int(*parr_t)[5];//新的类型名必须在 * 的右边
    typedef void(*pf_t)(int);
//int main()
//{
//    u_int a1; // == unsigned int a1;
//
//    pint_t p1; // == int* p1;
//
//    parr_t parr1; // == int(*parr1)[5]
//
//    pf_t pf1; // == void(*pf1)(int)
//}

//函数指针数组
    //数组是⼀个存放相同类型数据的存储空间，之前已经学习了指针数组
    //int* parr[10]; ////数组的存放了十个元素，元素类型是int* 

    //那么函数指针数组其实就是数组里面存放的内容是函数指针，也就是函数的地址
int add(int x, int y)
{
    return x + y;
}

int sub(int x, int y)
{
    return x - y;
}

int mul(int x, int y)
{
    return x * y;
}

int div(int x, int y)
{
    return x / y;
}
//int main()
//{
//    int (*pf1)(int, int) = Add; //pf1是函数指针变量
//    int (* pfarr1[4])(int, int) = { Add, Sub, Mul, Div }; //pfarr1就是函数指针数组
//    //这里涉及到了前面学到的一个注意点：[]的优先级要高于*号的
//    //所以在不加括号的情况下pfarr1是先和[4]结合表示是数组，
//    //然后去掉pfarr1[4]得到的int(*)(int, int)就是该数组里面存放的内容的类型，即函数指针类型
//}

//转移表
    //函数指针数组的用途：转移表
    //举例：计算器的一般实现：
//int main()
//{
//    int x, y;
//    int input = 1;
//    int ret = 0;
//    do
//    {
//        printf("*************************\n");
//        printf("****  1:add   2:sub  ****\n");
//        printf("****  3:mul   4:div  ****\n");
//        printf("****  0:exit         ****\n");
//        printf("*************************\n");
//        printf("请选择：");
//        scanf("%d", &input);
//        switch (input)
//        {
//        case 1:
//            printf("输入操作数：");
//            scanf("%d %d", &x, &y);
//            ret = add(x, y);
//            printf("ret = %d\n", ret);
//            break;
//        case 2:
//            printf("输入操作数：");
//            scanf("%d %d", &x, &y);
//            ret = sub(x, y);
//            printf("ret = %d\n", ret);
//            break;
//        case 3:
//            printf("输入操作数：");
//            scanf("%d %d", &x, &y);
//            ret = mul(x, y);
//            printf("ret = %d\n", ret);
//            break;
//        case 4:
//            printf("输入操作数：");
//            scanf("%d %d", &x, &y);
//            ret = div(x, y);
//            printf("ret = %d\n", ret);
//            break;
//        case 0:
//            printf("退出计算机\n");
//            break;
//        default:
//            printf("选择错误，重新选择\n");
//            break;
//        }
//    } while (input);
//    return 0;
//} //我们会发现如果我们想要拓展计算机的功能实现更多的算法，则switch语句会更加长

    //而如果用函数指针数组来实现则会非常简洁：
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    int(*pfArr[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 
    //                           0   1    2    3    4 为了和菜单的数字匹配，在数组内容补一个0
    do
    {
        printf("*************************\n");
        printf("****  1:add   2:sub  ****\n");
        printf("****  3:mul   4:div  ****\n");
        printf("****  0:exit         ****\n");
        printf("*************************\n");
        printf("请选择：");
        scanf("%d", &input);
        if ((input >= 1 && input <= 4))
        {
            printf("输入操作数：");
            scanf("%d %d", &x, &y);
            ret = (pfArr[input])(x, y); //input就是函数指针数组pfArr对应的下标
            printf("ret = %d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出计算器\n");
        }
        else
        {
            printf("输入错误，重新输入\n");
        }
    } while (input);
    return 0;
}